const defAttr = () => ({
    gl: null,
    children: [],
    programs: new Map(),
    drawObjs: new Map(),
})

//世界对象
export default class Scene {
    constructor(attr) {
        Object.assign(this, defAttr(), attr);

        this.drawId = 0;//渲染ID/ 用来检索
    }

    init() {
        const { gl, children } = this;
        children.forEach(element => {
            element.init(gl);
        });
    }

    regProgram(programName, { program, attribs, uniforms }) {
        const { programs, gl } = this;
        if (typeof programs.get(programName) != 'undefined') {
            console.log(`[${programName}]以注册`)
            return;
        }

        let attribNames = new Map();
        let uniformNames = new Map();

        gl.useProgram(program);

        for (let att of attribs) {
            attribNames.set(att, gl.getAttribLocation(program, att));
        }
        for (let uni of uniforms) {
            uniformNames.set(uni, gl.getUniformLocation(program, uni));
        }
        programs.set(programName, {
            program: program,
            attribs: attribNames,
            uniforms: uniformNames
        });

    }

    _getChildren(obj3D) {
        let arr = [];
        const child = obj3D.children;
        if (child.length > 0) {
            for (let obj of child) {
                for (let i of this._getChildren(obj)) {
                    arr.push(i);
                }
            }
        }
        for (let i of child) {
            arr.unshift(i);//头插
        }
        return arr;
    }


    add(...elements) {
        const { gl, children } = this;
        elements.forEach(u => {
            children.push(u);
            u.parent = this;

            u.init(gl);
            this.addDrawObjs(u);

            //递归出所有子类
            let child = this._getChildren(u);
            for (let obj of child) {
                this.addDrawObjs(obj);
            }
        })
    }

    addRange(array) {
        const { gl, children } = this;
        for (let i = 0; i < array.length; i++) {
            children.push(array[i]);
            array[i].parent = this;

            array[i].init(gl);
            this.addDrawObjs(array[i]);

            //递归出所有子类
            let child = this._getChildren(array[i]);
            for (let obj of child) {
                this.addDrawObjs(obj);
            }
        }
    }

    //如果父类对象已经加入scene中后再向父类对象添加元素, 则需要手动调用一次
    addDrawObjs(element) {
        const { drawObjs } = this;

        if (element.mat == null)
            return;

        const key = element.mat.program;
        //查询key是否存在
        if (typeof drawObjs.get(key) == 'undefined') {
            //插入新的
            drawObjs.set(key, [element]);
        } else {
            //添加上
            drawObjs.get(key).push(element);
        }
        element.drawId = this.drawId;
        this.drawId++;
    }

    remove(element) {
        const { children, drawObjs } = this;
        const i = children.indexOf(element);
        if (i != -1) {
            children.splice(i, 1);

            //递归出所有子类
            let child = this._getChildren(element);
            for (let obj of child) {
                this.removeDrawObjs(obj);
            }
        }
    }

    //如果父类对象已经加入scene中后再向父类对象添加元素, 则需要手动调用一次
    removeDrawObjs(element) {
        const { drawObjs } = this;
        const key = element.mat.program;
        const index = drawObjs.get(key).indexOf(element);
        drawObjs.get(key).splice(index, 1);
    }

    setUniform(name, uniform) {
        const { children } = this;
        children.forEach(u => {
            u.setUniform(name, uniform);
        })
    }

    setAttrib(name, attrib) {
        const { children } = this;
        children.forEach(u => {
            u.setAttrib(name, attrib);
        })
    }

    draw([r, g, b, a] = [0, 0, 0, 1]) {
        const { gl, programs, drawObjs } = this;

        gl.clearColor(r, g, b, a);
        gl.clear(gl.COLOR_BUFFER_BIT);

        for (let [key, value] of drawObjs) {
            const { program, attribs, uniforms } = programs.get(key);
            gl.useProgram(program);

            value.forEach(element => {
                element.update(gl, attribs, uniforms);
                element.draw(gl);
            });
        }
    }

    getLocation(programName, locationName) {
        const { programs } = this;
        let data = programs.get(programName);
        var name = locationName.split('_')[0];
        if (name == 'a') {
            return data.attribs.get(locationName);
        } else if (name == 'u') {
            return data.uniforms.get(locationName);
        } else {
            return null;
        }
    }

    allTransform() {
        const { children } = this;
        for (let obj of children) {
            obj.allTransform();
        }
    }
}